/*
 * Created by SharpDevelop.
 * User: EdwardW
 * Date: 30/06/2007
 * Time: 11:41
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */

using System;
using System.Collections.ObjectModel;
using System.Globalization;
using WatiN.Core;
using Wax.Properties;

namespace Wax
{
	/// <summary>
	/// Using the <see cref="FindMethod.Arguments" /> and the <see cref="MethodType" /> locates
	/// a particular <see cref="WatiN.Core.Element" />.
	/// </summary>
	[Serializable]
    public class FindMethod
	{
		#region Private fields
		
		/// <summary>
		/// Array of arguments that are used to execute a <see cref="WatiN.Core.Find" /> method.
		/// </summary>
		private Collection<string> arguments;
		
		/// <summary>
		/// The type of <see cref="WatiN.Core.Find" /> method to be used.
		/// </summary>
		private FindMethodTypes methodType;
		
		#endregion
		
		#region Constructors
		
		/// <summary>
		/// Creates a new instance of the <see cref="FindMethod"/> class.
		/// </summary>
		public FindMethod()
		{
			this.arguments = new Collection<string>();
		}
		
		#endregion
		
		#region Public properties
		
		/// <summary>
		/// Array of arguments that are used to execute a <see cref="WatiN.Core.Find" /> method.
		/// </summary>
		public Collection<string> Arguments
		{
			get { return this.arguments; }
		}
		
		/// <summary>
		/// The methodType of <see cref="WatiN.Core.Find" /> method to be used.
		/// </summary>
		public FindMethodTypes MethodType
		{
			get { return this.methodType; }
		}
		
		/// <summary>
		/// The value used in the Find method to locate the element.
		/// </summary>
		public string Value
		{
			get { return this.Arguments[0]; }
		}
		
		/// <summary>
		/// The additonal value used in the Find method to locate the element.
		/// </summary>
		public string AdditionalValue
		{
			get 
			{
				if (this.Arguments.Count > 1)
				{
					return this.Arguments[1];
				}
				else
				{
					return null;
				}
			}
		}
		
		#endregion
		
		#region Public static methods
		
		/// <summary>
		/// Parses input parameters to return a new instance of <see cref="FindMethod" />.
        /// <para>The list of supported find methods are as follows:
        /// <list methodType="table">
        /// 	<item>
        /// 		<term><see cref="FindMethodTypes.ById">ById</see></term>
        /// 		<description>
        ///             <para>Used to located an element by it's Id.</para>
        ///             <para><b>Required:</b> Id. Example: ById, new string[] {"btnOkay"}</para>
        ///         </description>
        /// 	</item>
        /// 	<item>
        /// 		<term><see cref="FindMethodTypes.ByLastElement">ByLastElement</see></term>
        /// 		<description>
        ///             <para>Use that last element found, from the previous command execution.</para>
        ///         </description>
        /// 	</item>
        /// 	<item>
        /// 		<term><see cref="FindMethodTypes.ByName">ByName</see></term>
        /// 		<description>
        ///             <para>Used to located an element by it's name.</para>
        ///             <para><b>Required arguments:</b> Name. Example: ByName, new string[] {"btnOkay"}</para>
        ///         </description>
        /// 	</item>
        /// 	<item>
        /// 		<term><see cref="FindMethodTypes.ByNameAndValue">ByNameAndValue</see></term>
        /// 		<description>
        ///             <para>Used to located an element by it's name and value, useful for locating radio buttons or checkboxes.</para>
        ///             <para><b>Required arguments:</b> Name and value. Example: ByNameAndValue, new string[] {"radio1", "2"} where 
        ///             radio1 is the element's name and 2 is it's value.
        ///             </para>
        ///         </description>
        /// 	</item>
        /// 	<item>
        /// 		<term><see cref="FindMethodTypes.ByTitle">ByTitle</see></term>
        /// 		<description>
        ///             <para>Used to located an element by it's title attribute.</para>
        ///             <para><b>Required arguments:</b> Title. Example: ByTitle, new string[] {"Click on this link"}</para>
        ///         </description>
        /// 	</item>
        /// 	<item>
        /// 		<term><see cref="FindMethodTypes.ByValue">ByValue</see></term>
        /// 		<description>
        ///             <para>Used to located an element by it's value.</para>
        ///             <para><b>Required arguments:</b> Value. Example: ByValue, new string[] {"visa"}</para>
        ///         </description>
        /// 	</item>
        /// </list>
		/// </para>
		/// </summary>
		/// <param name="findMethod">Partial string representation of a <see cref="FindMethodTypes" /></param>
		/// <param name="findMethodArgs">Arguments used by the find method to locate a <see cref="WatiN.Core.Element" />.</param>
		/// <returns>The find method for the specified method and arguments</returns>
		/// <exception cref="InvalidFindMethodException">If the find method is unknown or invalid.</exception>
		public static FindMethod Parse(string findMethod, string[] findMethodArgs)
		{
			if (string.IsNullOrEmpty(findMethod))
			{
				throw new ArgumentNullException("findMethod");
			}
			
			if (findMethodArgs == null)
			{
				throw new ArgumentNullException("findMethodArgs");
			}
			
			FindMethod returnValue = new FindMethod();
			returnValue.arguments = new Collection<string>(findMethodArgs);
			returnValue.methodType = ParseFindMethodType(findMethod);
			
			return returnValue;
		}
		
		/// <summary>
		/// Parses a partial string representation of a <see cref="FindMethodTypes" />. For example "ById" will
		/// be parsed and returned as <see cref="FindMethodTypes.ById" />.
		/// </summary>
		/// <param name="methodType">Partial string representation of a <see cref="FindMethodTypes" /></param>
		/// <returns>Parsed value as a <see cref="FindMethodTypes" /></returns>
		public static FindMethodTypes ParseFindMethodType(string methodType)
		{
            if (string.IsNullOrEmpty(methodType))
            {
                throw new ArgumentNullException("methodType");
            }

			FindMethodTypes returnValue;

		    if (methodType.Equals("byid", StringComparison.CurrentCultureIgnoreCase))
		    {
		        returnValue = FindMethodTypes.ById;
		    }
		    else if (methodType.Equals("byname", StringComparison.CurrentCultureIgnoreCase))
		    {
		        returnValue = FindMethodTypes.ByName;
		    }
		    else if (methodType.Equals("byvalue", StringComparison.CurrentCultureIgnoreCase))
		    {
		        returnValue = FindMethodTypes.ByValue;
		    }
            else if (methodType.Equals("bynameandvalue", StringComparison.CurrentCultureIgnoreCase))
		    {
		        returnValue = FindMethodTypes.ByNameAndValue;
		    }
            else if (methodType.Equals("bytitle", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = FindMethodTypes.ByTitle;
            }
            else if (methodType.Equals("bylastelement", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = FindMethodTypes.ByLastElement;
            }
            else if (methodType.Equals("usingdocument", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = FindMethodTypes.UsingDocument;
            }
            else if (methodType.Equals("byurl", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = FindMethodTypes.ByUrl;
            }
            else if (methodType.Equals("byalt", StringComparison.CurrentCultureIgnoreCase))
            {
                returnValue = FindMethodTypes.ByAlt;
            }
		    else
		    {
		        throw new InvalidFindMethodException(methodType);
		    }

		    return returnValue;
		}
		
		#endregion
		
		#region Public instance methods
		
		/// <summary>
		/// Executes this find method
		/// </summary>
		/// <param name="context">The execution context used to execute this find method</param>
		[CLSCompliant(false)]
		public WatiN.Core.Element Execute(IExecutionContext context)
		{
			WatiN.Core.Element returnValue;
			
			switch (this.MethodType)
			{
				case FindMethodTypes.ById:
					returnValue = this.FindById(context);
					break;
				case FindMethodTypes.ByName:
					returnValue = this.FindByName(context);
					break;
				case FindMethodTypes.ByValue:
					returnValue = this.FindByValue(context);
					break;
				case FindMethodTypes.ByNameAndValue:
					returnValue = this.FindByNameAndValue(context);
					break;
                case FindMethodTypes.ByTitle:
                    returnValue = this.FindByTitle(context);
                    break;
                case FindMethodTypes.ByUrl:
                    returnValue = this.FindByHref(context);
                    break;
                case FindMethodTypes.ByLastElement:
                    returnValue = context.LastFoundElement;
			        break;
                default:
					throw new ArgumentOutOfRangeException("context", string.Format(CultureInfo.InvariantCulture, "Do not know how to execute the find method methodType: {0}", this.MethodType));
			}
			
			if(returnValue == null || !returnValue.Exists)
			{
				throw new FindMethodException(this.Value, this.MethodType.ToString(), this.AdditionalValue);
			}
			
            if (this.MethodType != FindMethodTypes.ByLastElement)
            {
                context.LastFoundElement = returnValue;
            }

			return returnValue;
		}
		
		#endregion
		
		#region Private instance methods
		
		/// <summary>
		/// Looks up an <see cref="WatiN.Core.Element">element</see> by <see cref="Find.ByName(string)" /> and <see cref="Find.ByValue(string)" />.
		/// </summary>
		/// <param name="context"></param>
		private WatiN.Core.Element FindByNameAndValue(IExecutionContext context)
		{
			if (this.Arguments.Count != 2)
			{
				throw new FindMethodException(Resource.FindMethodFindByNameAndValueArgsException);
			}
			
			return context.Browser.Element(WatiN.Core.Find.ByName(this.Value) & WatiN.Core.Find.ByValue(this.AdditionalValue));
		}
		
        /// <summary>
		/// Looks up an <see cref="WatiN.Core.Element">element</see> using <see cref="Find.ByUrl(string)" />.
		/// </summary>
		/// <param name="context"></param>
        private WatiN.Core.Element FindByHref(IExecutionContext context)
        {
            return context.Browser.Element(WatiN.Core.Find.ByUrl(this.Value));
        }


	    /// <summary>
		/// Looks up an <see cref="WatiN.Core.Element">element</see> using <see cref="Find.ByValue(string)" />.
		/// </summary>
		/// <param name="context"></param>
		private WatiN.Core.Element FindByValue(IExecutionContext context)
		{
			return context.Browser.Element(WatiN.Core.Find.ByValue(this.Value));
		}

        /// <summary>
        /// Looks up an <see cref="WatiN.Core.Element">element</see> using <see cref="Find.ByTitle(string)" />.
        /// </summary>
        /// <param name="context"></param>
        private WatiN.Core.Element FindByTitle(IExecutionContext context)
        {
            return context.Browser.Element(WatiN.Core.Find.ByTitle(this.Value));
        }

		/// <summary>
		/// Looks up an <see cref="WatiN.Core.Element">element</see> using <see cref="Find.ById(string)" />.
		/// </summary>
		/// <param name="context"></param>
		private WatiN.Core.Element FindById(IExecutionContext context)
		{
			return context.Browser.Element(WatiN.Core.Find.ById(this.Value));
		}
		
		/// <summary>
		/// Looks up an <see cref="WatiN.Core.Element">element</see> using <see cref="Find.ByName(string)" />.
		/// </summary>
		/// <param name="context"></param>
		private WatiN.Core.Element FindByName(IExecutionContext context)
		{
			return context.Browser.Element(WatiN.Core.Find.ByName(this.Value));
		}
		#endregion
	}
}
